Skip to content

GH-3993: Fix async race condition in TcpOutGateway #3995

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 23, 2023

Conversation

artembilan
Copy link
Member

Fixes #3993

When TcpOutboundGateway is in an async mode and CCF is configured not for singleUse an Semaphore around an obtained TcpConnection is involved. If we fail on TcpConnection.send(), resources are not clean up, including the mentioned Semaphore: in async mode this happens only when we receive a reply.

  • Catch an exception on the TcpConnection.send() and perform cleanUp() in async mode.
  • Add cleanUp() into a scheduled task from the TcpOutboundGateway.AsyncReply when no reply arrives in time.
  • Optimize TcpOutboundGateway.AsyncReply behavior to cancel no-reply scheduled task when reply arrives into a CompletableFuture

Cherry-pick to 5.5.x

Fixes spring-projects#3993

When `TcpOutboundGateway` is in an `async` mode and `CCF` is configured
not for `singleUse` an `Semaphore` around an obtained `TcpConnection` is involved.
If we fail on `TcpConnection.send()`, resources are not clean up, including the
mentioned `Semaphore`: in async mode this happens only when we receive a reply.

* Catch an exception on the `TcpConnection.send()` and perform `cleanUp()` in async mode.
* Add `cleanUp()` into a scheduled task from the `TcpOutboundGateway.AsyncReply`
when no reply arrives in time.
* Optimize `TcpOutboundGateway.AsyncReply` behavior to cancel no-reply scheduled task
when reply arrives into a `CompletableFuture`

**Cherry-pick to `5.5.x`**
this.noResponseFuture =
getTaskScheduler()
.schedule(() -> {
cleanUp(this.haveSemaphore, this.connection, this.connection.getConnectionId());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's a new race here; we could end up releasing the semaphore twice because onMessage calls cleanup before completing the future.

Perhaps check if pendingReplies was actually removed in cleanup before releasing the semaphore?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See some fixed I have pushed.

only if `future.completeExceptionally()` is `true`
@garyrussell garyrussell merged commit ebba374 into spring-projects:main Jan 23, 2023
@garyrussell
Copy link
Contributor

Needs back port due to ListenableFuture and no .thenApply().

@garyrussell
Copy link
Contributor

Forget that; since it's internal, we can use CompletableFuture.

garyrussell pushed a commit that referenced this pull request Jan 23, 2023
* GH-3993: Fix async race condition in TcpOutGateway

Fixes #3993

When `TcpOutboundGateway` is in an `async` mode and `CCF` is configured
not for `singleUse` an `Semaphore` around an obtained `TcpConnection` is involved.
If we fail on `TcpConnection.send()`, resources are not clean up, including the
mentioned `Semaphore`: in async mode this happens only when we receive a reply.

* Catch an exception on the `TcpConnection.send()` and perform `cleanUp()` in async mode.
* Add `cleanUp()` into a scheduled task from the `TcpOutboundGateway.AsyncReply`
when no reply arrives in time.
* Optimize `TcpOutboundGateway.AsyncReply` behavior to cancel no-reply scheduled task
when reply arrives into a `CompletableFuture`

**Cherry-pick to `5.5.x`**

* * Call `cleanUp()` from no response scheduled task
only if `future.completeExceptionally()` is `true`
@garyrussell
Copy link
Contributor

...and cherry-picked as 1aef041 after resolving conflicts.

@artembilan
Copy link
Member Author

That's OK, Gary.
We indeed can use it internally.
However the AbstractMessageProducingHandler cannot handle CompletableFuture in that 5.5.x version:

if (this.async && (reply instanceof ListenableFuture<?> || reply instanceof Publisher<?>)) {

So, your catch is good.

I'll provide the fix in subsequent commit...

@artembilan artembilan deleted the GH-3993 branch January 23, 2023 16:22
@garyrussell
Copy link
Contributor

Oops - compile error too.

@artembilan
Copy link
Member Author

An adaptation to ListenableFuture: 070d186

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

TcpOutboundGateway. Semaphore not released after exception
2 participants